// BASED ON:
// http://cropperplugins.codeplex.com/
// Dino Chiesa
// Tue, 14 Dec 2010  12:31
//
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace System.Security.OAuth
{
    /// <summary>
    /// A class to manage OAuth interactions.  This works with Twitter, not sure about other OAuth-enabled services.
    /// </summary>
    /// <remarks>
    /// This class holds the relevant OAuth parameters, and exposes methods that do things, based on those parameters.
    /// </remarks>
    public class OAuthManager
    {
        /// <summary>
        /// The "starting" date for all UNIX timestamps, which OAuth uses.
        /// </summary>
        private static readonly DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
        /// <summary>
        /// The parameters of the OAuth request.
        /// </summary>
        private SortedDictionary<string, string> parameters;
        /// <summary>
        /// Used by the nonce generator.
        /// </summary>
        private Random _random;

        /// <summary>
        /// The default public constructor.
        /// </summary>
        /// <remarks>
        /// Initializes various fields to default values.
        /// </remarks>
        public OAuthManager()
        {
            _random = new Random();
            parameters = new SortedDictionary<string,string>();
            parameters.Add("signature_method", "HMAC-SHA1");
            parameters.Add("version", "1.0");
            parameters.Add("consumer_key", "");
            parameters.Add("consumer_secret", "");
            parameters.Add("token", "");
            parameters.Add("token_secret", "");
        }

        /// <summary>
        ///   The constructor to use when using OAuth when you already have an OAuth access token.
        /// </summary>
        /// <param name="consumerKey">Identifies which application is making the request.</param>
        /// <param name="consumerSecret">Identifies which application is making the request.</param>
        /// <param name="token">Typically represents a user's permission to share access to their account with your application.</param>
        /// <param name="tokenSecret">Typically represents a user's permission to share access to their account with your application.</param>
        public OAuthManager(string consumerKey, string consumerSecret, string token, string tokenSecret) : this()

        {
            parameters["consumer_key"] = consumerKey;
            parameters["consumer_secret"] = consumerSecret;
            parameters["token"] = token;
            parameters["token_secret"] = tokenSecret;
        }

        /// <summary>
        /// Identifies which application is making the request.
        /// </summary>
        public string ConsumerKey
        {
            get
            {
                return parameters["consumer_key"];
            }
            set
            {
                parameters["consumer_key"] = value;
            }
        }

        /// <summary>
        /// Identifies which application is making the request.
        /// </summary>
        public string ConsumerSecret
        {
            get
            {
                return parameters["consumer_secret"];
            }
            set
            {
                parameters["consumer_secret"] = value;
            }
        }

        /// <summary>
        /// Typically represents a user's permission to share access to their account with your application.
        /// </summary>
        public string Token
        {
            get
            {
                return parameters["token"];
            }
            set
            {
                parameters["token"] = value;
            }
        }
        
        /// <summary>
        /// Typically represents a user's permission to share access to their account with your application.
        /// </summary>
        public string TokenSecret
        {
            get
            {
                return parameters["token_secret"];
            }
            set
            {
                parameters["token_secret"] = value;
            }
        }

        /// <summary>
        /// Generate the timestamp for the signature.
        /// </summary>
        /// <returns>The timestamp, in string form.</returns>
        private string GenerateTimeStamp()
        {
            // UNIX time is represented as the number of seconds since the epoch
            TimeSpan ts = DateTime.UtcNow - unixEpoch;
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        /// <summary>
        ///   Renews the nonce and timestamp on the oauth parameters.
        /// </summary>
        /// <remarks>
        ///     Each new request should get a new, current timestamp, and a nonce. This helper method does both of those things. This gets
        /// </remarks>
        private void NewRequest()
        {
            parameters["nonce"] = GenerateNonce();
            parameters["timestamp"] = GenerateTimeStamp();
        }

        /// <summary>
        /// Generate an oauth nonce.
        /// </summary>
        /// <remarks>
        /// According to RFC 5849, A nonce is a random string, uniquely generated by the client to allow the server to verify that a request has never been made before an helps prevent replay attacks when requests are made over a non-secure channel.  The nonce value MUST be unique across all requests with the same timestamp, client  credentials, and token combinations.
        /// </remarks>
        /// <returns>the nonce</returns>
        private string GenerateNonce()
        {
            /*
             * 
             * One way to implement the nonce is just to use a
             * monotonically-increasing integer value.  It starts at zero and
             * increases by 1 for each new request or signature generated.
             * Keep in mind the nonce needs to be unique only for a given
             * timestamp!  So if your app makes less than one request per
             * second, then using a static nonce of "0" will work.
             * 
             * Most OAuth nonce generation routines are waaaaay over-engineered,
             * and this one is no exception.
             * 
             */

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 8; i++)
            {
                int g = _random.Next(3);
                switch(g)
                {
                    case 0:
                        // lowercase alpha
                        sb.Append((char)(_random.Next(26)+97), 1);
                        break;
                    default:
                        // numeric digits
                        sb.Append((char)(_random.Next(10)+48), 1);
                        break;
                }
            }
            return sb.ToString();
        }


        /// <summary>
        /// Internal function to extract from a URL all query string parameters that are not related to OAuth - in other words all parameters not beginning with "oauth_".
        /// </summary>
        /// <remarks>
        /// For example, given a url like http://foo?a=7&guff, the returned value will be a Dictionary of string-to-string relations.  There will be 2 entries in the Dictionary: "a"=>7, and "guff"=>"".
        /// </remarks>
        /// <param name="queryString">The query string part of the Url</param>
        /// <returns>A SortedDictionary containing the set of parameter names and associated values</returns>
        private SortedDictionary<string,string> ExtractQueryParameters(string queryString)
        {
            // The parsing loop should only be the key/value pairs, not the "?"
            if (queryString.StartsWith("?"))
            {
                queryString = queryString.Remove(0, 1);
            }

            SortedDictionary<string, string> result = new SortedDictionary<string,string>();

            if (string.IsNullOrEmpty(queryString))
            {
                return result;
            }

            foreach (string s in queryString.Split('&'))
            {
                if (!string.IsNullOrEmpty(s) && !s.StartsWith("oauth_"))
                {
                    if (s.IndexOf('=') > -1)
                    {
                        string[] temp = s.Split('=');
                        result.Add(temp[0], temp[1]);
                    }
                    else
                    {
                        result.Add(s, string.Empty);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Formats the list of request parameters into string a according to the requirements of OAuth. The resulting string could be used in the Authorization header of the request.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     The output of this is not suitable for signing.
        ///   </para>
        ///   <para>
        ///     There are 2 formats for specifying the list of OAuth parameters in the OAuth spec: one suitable for signing, and the other suitable for use within Authorization HTTP Headers. This method emits a string suitable for the latter.
        ///   </para>
        /// </remarks>
        /// <param name="parameters">The Dictionary of parameters. It need not be sorted.</param>
        /// <returns>a string representing the parameters</returns>
        private static string EncodeRequestParameters(ICollection<KeyValuePair<string,string>> p)
        {
            StringBuilder sb = new StringBuilder();
            foreach (KeyValuePair<string,string> item in p)
            {
                if (!string.IsNullOrEmpty(item.Value) && !item.Key.EndsWith("secret"))
                {
                    sb.AppendFormat("oauth_{0}=\"{1}\", ", item.Key, UrlEncode(item.Value));
                }
            }
            return sb.ToString().TrimEnd(' ').TrimEnd(',');
        }

        /// <summary>
        ///   Generate a string to be used in an Authorization header in an HTTP request.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     This method assembles the available oauth_ parameters that have been set in the Dictionary in this instance, produces the signature base (As described by the OAuth spec, RFC 5849), signs it, then re-formats the oauth_ parameters into the appropriate form, including the oauth_signature value, and returns the result.
        ///   </para>
        ///   <para> 
        ///      If you pass in a non-null, non-empty realm, this method will include the realm='foo' clause in the Authorization header.
        ///   </para>
        /// </remarks>
        /// <seealso cref='GenerateAuthorizationHeader' />
        public string GenerateCredentialsHeader(string uri, string method, string realm)
        {
            NewRequest();
            return GetAuthorizationHeader(uri, method, realm);
        }


        /// <summary>
        ///   Generate a string to be used in an Authorization header in an HTTP request.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     This method assembles the available oauth_ parameters that have been set in the Dictionary in this instance, produces the signature base (As described by the OAuth spec, RFC 5849), signs it, then re-formats the oauth_ parameters into the appropriate form, including the oauth_signature value, and returns the result.
        ///   </para>
        /// </remarks>
        public string GenerateAuthorizationHeader(string uri, string method)
        {
            NewRequest();
            return GetAuthorizationHeader(uri, method, null);
        }

        /// <summary>
        ///   Generate a string to be used in an Authorization header in an HTTP request.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     This method assembles the available oauth_ parameters that have been set in the Dictionary in this instance, produces the signature base (As described by the OAuth spec, RFC 5849), signs it, then re-formats the oauth_ parameters into the appropriate form, including the oauth_signature value, and returns the result.
        ///   </para>
        /// </remarks>
        private string GetAuthorizationHeader(string uri, string method)
        {
            return GetAuthorizationHeader(uri, method, null);
        }


        private string GetAuthorizationHeader(string uri, string method, string realm)
        {
            if (string.IsNullOrEmpty(this.parameters["consumer_key"]))
            {
                throw new ArgumentNullException("consumer_key");
            }

            if (string.IsNullOrEmpty(this.parameters["signature_method"]))
            {
                throw new ArgumentNullException("signature_method");
            }

            Sign(uri, method);

            string erp = EncodeRequestParameters(this.parameters);
            return (string.IsNullOrEmpty(realm)) ? "OAuth " + erp : string.Format("OAuth realm=\"{0}\", ", realm) + erp;
        }


        private void Sign(string uri, string method)
        {
            string signatureBase = GetSignatureBase(uri, method);
            HashAlgorithm hash = GetHash();

            byte[] dataBuffer = System.Text.Encoding.UTF8.GetBytes(signatureBase);
            byte[] hashBytes = hash.ComputeHash(dataBuffer);

            parameters["signature"] = Convert.ToBase64String(hashBytes);
        }

        /// <summary>
        /// Formats the list of request parameters into "signature base" string as defined by RFC 5849.  This will then be MAC'd with a suitable hash.
        /// </summary>
        private string GetSignatureBase(string url, string method)
        {
            // normalize the URI
            Uri uri = new Uri(url);
            string normUrl = string.Format("{0}://{1}", uri.Scheme, uri.Host);
            if (!((uri.Scheme == "http" && uri.Port == 80) ||
                  (uri.Scheme == "https" && uri.Port == 443)))
            {
                normUrl += ":" + uri.Port;
            }

            normUrl += uri.AbsolutePath;

            // the sigbase starts with the method and the encoded URI
            StringBuilder sb = new StringBuilder();
            sb.Append(method);
            sb.Append('&');
            sb.Append(UrlEncode(normUrl));
            sb.Append('&');

            // the parameters follow - all OAuth parameters plus any parameters on the URI
            // each URI may have a distinct set of query parameters
            SortedDictionary<string,string> p = ExtractQueryParameters(uri.Query);
            // add all non-empty parameters to the "current" parameters
            foreach (KeyValuePair<string,string> p1 in this.parameters)
            {
                // Exclude all OAuth parameters that are secret or signatures; any secrets should be kept to ourselves, and any existing signature will be invalid.
                if (!string.IsNullOrEmpty(this.parameters[p1.Key]) && !p1.Key.EndsWith("_secret") && !p1.Key.EndsWith("signature"))
                {
                    p.Add("oauth_" + p1.Key, p1.Value);
                }
            }

            // concatenate+format all those parameters
            StringBuilder sb1 = new StringBuilder();
            foreach (KeyValuePair<string, string> item in p)
            {
                // even "empty" parameters need to be encoded this way.
                sb1.AppendFormat("{0}={1}&", item.Key, item.Value);
            }

            // append the UrlEncoded version of that string to the sigbase
            sb.Append(UrlEncode(sb1.ToString().TrimEnd('&')));
            return sb.ToString();
        }

        private HashAlgorithm GetHash()
        {
            if (parameters["signature_method"] != "HMAC-SHA1")
            {
                throw new NotImplementedException();
            }

            string keystring = string.Format("{0}&{1}", UrlEncode(parameters["consumer_secret"]), UrlEncode(parameters["token_secret"]));
            HMACSHA1 hmacsha1 = new HMACSHA1();
            hmacsha1.Key = System.Text.Encoding.UTF8.GetBytes(keystring); 
            return hmacsha1;
        }

        /// <summary>
        ///   This is an oauth-compliant Url Encoder.  The default .NET encoder outputs the percent encoding in lower case.  While this is not a problem with the percent encoding defined in RFC 3986, OAuth (RFC 5849) requires that the characters be upper case throughout OAuth.
        /// </summary>
        /// <param name="value">The value to encode</param>
        /// <returns>the Url-encoded version of that string</returns>
        private static string UrlEncode(string value)
        {
            StringBuilder result = new System.Text.StringBuilder();
            foreach (char symbol in value)
            {
                if (unreservedChars.IndexOf(symbol) != -1)
                {
                    result.Append(symbol);
                }
                else
                {
                    result.Append('%' + string.Format("{0:X2}", (int)symbol));
                }
            }
            return result.ToString();
        }
        private static string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
    }
}

